#include "Dri_USART1.h"

/**
 * @brief USART1 初始化
 *
 */
void Dri_USART1_Init(void)
{
    // 1. 时钟使能 -------------------------------
    // 1.1 对 GPIOA 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    // 1.2 对 USART1 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

    // 2. 引脚设置 --------------------------------
    // 2.1 将 TX(PA9)引脚设置为复用推挽模式，MODE=11; CNF=10
    GPIOA->CRH |= GPIO_CRH_MODE9;
    GPIOA->CRH |= GPIO_CRH_CNF9_1;
    GPIOA->CRH &= ~GPIO_CRH_CNF9_0;
    // 2.2 将 RX(PA10) 引脚设置为浮空输入模式，MODE=00, CNF=01, 引脚默认即是浮空输入
    GPIOA->CRH &= ~GPIO_CRH_MODE10;
    GPIOA->CRH &= ~GPIO_CRH_CNF10_1;
    GPIOA->CRH |= GPIO_CRH_CNF10_0;

    // 3. USART1 配置 -----------------------------
    // 3.1 USART1 总使能
    USART1->CR1 |= USART_CR1_UE;
    // 3.2 USART1 发送使能
    USART1->CR1 |= USART_CR1_TE;
    // 3.3 USART1 接收使能
    USART1->CR1 |= USART_CR1_RE;
    // 3.4 数据帧设置: 设置数据位是8位（默认值）
    USART1->CR1 &= ~USART_CR1_M;
    // 3.5 数据帧设置：设置禁止校验位（默认值）
    USART1->CR1 &= ~USART_CR1_PCE;
    // 3.6 数据帧设置: 设置停止位为1位（默认值）
    USART1->CR2 &= ~USART_CR2_STOP;
    // 3.7 波特率设置：目标波特率为115200，分频系数需要是 39.0625; 39存入BRR寄存器4~15位， 0.0625存入BRR寄存器低4位
    USART1->BRR = (39 << 4) | 1;

    // NVIC 设置 ----------------------------------------
    NVIC_SetPriorityGrouping(3);      // 设置抢占优先级4位
    NVIC_EnableIRQ(USART1_IRQn);      // 中断向量使能
    NVIC_SetPriority(USART1_IRQn, 5); // 设置优先级
}

/**
 * @brief 发送1个字节的数据
 *
 * @param byte 要发送的字节
 */
void Dri_USART1_TransmitByte(uint8_t byte)
{
    // 等待上一个字节放入发送移位寄存器，TXE 标志位如果为0，就一直等待
    while ((USART1->SR & USART_SR_TXE) == 0)
        ;

    // 将要发送的字节数据写入TDR， 同时清除TXE标志位
    USART1->DR = byte;
}

/**
 * @brief 发送多个字节数据
 *
 * @param bytes 要发送的多个字节
 * @param len 要发送的字节个数
 */
void Dri_USART1_TransmitBytes(uint8_t *bytes, uint8_t len)
{
    // 循环，挨个字节发送
    for (uint8_t i = 0; i < len; i++)
    {
        Dri_USART1_TransmitByte(bytes[i]);
    }
}

/**
 * @brief 接收1个字节的数据, 需要在死循环中被轮询调用
 *
 * @return uint8_t 返回接收到的字节
 */
uint8_t Dri_USART1_ReceiveByte(void)
{
    // 等待数据接收完成, 标志位RXEN如果为0就一直循环
    while ((USART1->SR & USART_SR_RXNE) == 0)
        ;

    // 返回接收到的数据（1个字节）
    return USART1->DR;
}

/**
 * @brief 接收多个字节数据，也需要在死循环中被轮询调用
 *
 * @param bytes 将接收的多个字节保存至该地址处
 * @param len 将接收到的字节个数存入该地址处
 */
void Dri_USART1_ReceiveBytes(uint8_t *bytes, uint8_t *len)
{
    *len = 0;

    // 循环, 挨个字节接收
    while (1)
    {
        // 挨个字节接收, RXNE置1表示接收完1个字节
        while ((USART1->SR & USART_SR_RXNE) == 0)
        {
            // 如果检测到空闲状态，结束本函数
            if (USART1->SR & USART_SR_IDLE)
            {
                // 清除IDLE标志位:先读SR，再读DR； SR已经读过了，只需读DR即可清除
                USART1->DR;
                return;
            }
        }

        // 将RDR数据存入bytes中
        bytes[*len] = USART1->DR;

        // 接收长度+1
        (*len)++;
    }
}

/**
 * @brief 开始使用非阻塞方式接收数据，一直接收到检测到空闲状态
 *
 */
void Dri_USART1_ReceiveBytesITStart(void)
{
    // 开启中断使能 RXNE
    USART1->CR1 |= USART_CR1_RXNEIE;
    // 开启中断使能 IDLE
    USART1->CR1 |= USART_CR1_IDLEIE;
}

/**
 * @brief 停止非阻塞方式接收数据
 *
 */
void Dri_USART1_ReceiveBytesITStop(void)
{
    // 关闭中断使能 RXNE
    USART1->CR1 &= ~USART_CR1_RXNEIE;
    // 关闭中断使能 IDLE
    USART1->CR1 &= ~USART_CR1_IDLEIE;
}

/*******************************************************************************/
// 定义弱函数，非阻塞方式接收完多个字节之后调用该弱函数,
__weak void Dri_USART1_ReceiveCallback(uint8_t *buffers, uint8_t len)
{}


/*******************************************************************************/
// 给中断向量 USART1 定义中断服务函数
void USART1_IRQHandler(void)
{
    // 定义静态变量（数组），用于保存接收到的字节数据
    static uint8_t rx_buffs[255];
    // 定义静态变量，用于保存接收到字节个数（长度）
    static uint8_t rx_len = 0;

    // 判断，接收到1个字节的数据
    if (USART1->SR & USART_SR_RXNE)
    {
        // 将接收到字节存入数组
        rx_buffs[rx_len] = USART1->DR;
        // 接收长度加1
        rx_len++;
    }

    // 判断，接收完多个数据并检测到空闲状态
    if (USART1->SR & USART_SR_IDLE)
    {
        // 调用弱函数，并将接收到数据和长度传出
        Dri_USART1_ReceiveCallback(rx_buffs, rx_len);
        // 长度清零
        rx_len = 0;
        // 读DR以清除IDEL标志位
        USART1->DR;
    }
}

/*******************************************************************************/
// 重定义fputc
int fputc( int ch, FILE *stream )
{
    // 使用串口发送 ch
    Dri_USART1_TransmitByte(ch);
    // 返回值
    return ch;
}
